#include "../../include/helpers/resource.h"
#include "../../include/helpers/general.h"

#include <codecvt>
#include <locale>
#include <memory>
#include <string>
#include <vector>

namespace ohos_rive {
std::unordered_map<std::string, napi_ref> g_classRegistry;

std::string GetClassName(napi_env env, napi_value cls)
{
    if (!env || !cls) {
        return "";
    }

    napi_valuetype type;
    napi_typeof(env, cls, &type);
    if (type != napi_function) {
        return "";
    }

    napi_value nameValue;
    napi_status status = napi_get_named_property(env, cls, "name", &nameValue);
    if (status != napi_ok) {
        return "";
    }

    char buffer[256] = {0};
    size_t length = 0;
    status = napi_get_value_string_utf8(env, nameValue, buffer, sizeof(buffer) - 1, &length);
    if (status != napi_ok) {
        return "";
    }

    return std::string(buffer, length);
}

std::string GetEnumName(napi_env env, napi_value enumName)
{
    std::string enumNameStr;
    size_t length = 0;
    if (enumName == nullptr) {
        LOGE("Failed to parse arguments for RegisterClass");
        return "";
    }
    auto status = napi_get_value_string_utf8(env, enumName, nullptr, 0, &length);
    if (status != napi_ok || length == 0) {
        LOGE("Failed to parse arguments for RegisterClass");
        return "";
    }
    enumNameStr.resize(length);
    status = napi_get_value_string_utf8(env, enumName, &enumNameStr[0], length + 1, &length);
    if (status != napi_ok) {
        LOGE("Failed to parse arguments for RegisterClass");
        return "";
    }
    return enumNameStr;
}

void RegisterClass(napi_env env, napi_value args[], size_t argc)
{
    std::string className;
    if (argc == 1) {
        className = GetClassName(env, args[0]);
    } else if (argc == 2) {
        className = GetEnumName(env, args[1]);
    }

    if (className.empty()) {
        return;
    }

    napi_ref classRef;
    napi_create_reference(env, args[0], 1, &classRef);
    g_classRegistry[className] = classRef;
}

void UnregisterClass(napi_env env, napi_value args[], size_t argc)
{
    std::string className;
    if (argc == 1) {
        className = GetClassName(env, args[0]);
    } else if (argc == 2) {
        className = GetEnumName(env, args[1]);
    }

    if (className.empty()) {
        return;
    }

    auto it = g_classRegistry.find(className);
    if (it != g_classRegistry.end()) {
        napi_delete_reference(env, it->second);
        g_classRegistry.erase(it);
    }
}

NativeResource<napi_value> FindClass(napi_env env, const char *name)
{
    if (!name) {
        return NativeResource<napi_value>(nullptr, env);
    }

    auto it = g_classRegistry.find(std::string(name));
    if (it != g_classRegistry.end()) {
        napi_value clazz;
        napi_status status = napi_get_reference_value(env, it->second, &clazz);
        if (status == napi_ok) {
            return NativeResource<napi_value>(clazz, env);
        }
    }

    return NativeResource<napi_value>(nullptr, env);
}

NativeResource<napi_value> GetObjectClass(napi_env env, napi_value obj)
{
    napi_value prototype;
    napi_status status = napi_get_prototype(env, obj, &prototype);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(prototype, env);
}

NativeResource<napi_value> GetStaticObjectField(napi_env env, napi_value clazz, const char *fieldName)
{
    napi_value value;
    napi_status status = napi_get_named_property(env, clazz, fieldName, &value);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(value, env);
}

NativeResource<napi_value> GetObjectFromMethod(napi_env env, napi_value obj, const char *methodName, ...)
{
    napi_value method;
    napi_status status = napi_get_named_property(env, obj, methodName, &method);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    napi_valuetype valueType;
    status = napi_typeof(env, method, &valueType);
    if (status != napi_ok || valueType != napi_function) {
        return NativeResource<napi_value>(nullptr, env);
    }

    va_list args;
    va_start(args, methodName);

    napi_value result = nullptr;
    status = napi_call_function(env, obj, method, 0, nullptr, &result);

    va_end(args);

    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(result, env);
}

NativeResource<napi_value> GetObjectArrayElement(napi_env env, napi_value jarray, size_t index)
{
    napi_value element;
    napi_status status = napi_get_element(env, jarray, static_cast<uint32_t>(index), &element);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(element, env);
}

NativeResource<napi_value> MakeObject(napi_env env, napi_value method, int argsCount, napi_value *args)
{
    napi_valuetype valueType;
    napi_status status = napi_typeof(env, method, &valueType);
    if (status != napi_ok || valueType != napi_function) {
        return NativeResource<napi_value>(nullptr, env);
    }

    napi_value result = nullptr;
    status = napi_new_instance(env, method, argsCount, args, &result);

    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(result, env);
}

NativeResource<napi_value> MakeNapiString(napi_env env, const char *str)
{
    if (!str) {
        return NativeResource<napi_value>(nullptr, env);
    }

    napi_value value;
    napi_status status = napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &value);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    return NativeResource<napi_value>(value, env);
}

NativeResource<napi_value> MakeNapiString(napi_env env, const std::string &str)
{
    return MakeNapiString(env, str.c_str());
}

std::vector<uint8_t> ByteArrayToUint8Vec(napi_env env, napi_value byteArray)
{
    std::vector<uint8_t> result;
    bool isArrayBuffer = false;
    napi_status status = napi_is_arraybuffer(env, byteArray, &isArrayBuffer);
    if (status != napi_ok || !isArrayBuffer) {
        uint32_t length = 0;
        if (napi_get_array_length(env, byteArray, &length) == napi_ok) {
            result.resize(length);
            for (uint32_t i = 0; i < length; i++) {
                napi_value element;
                if (napi_get_element(env, byteArray, i, &element) == napi_ok) {
                    double value;
                    if (napi_get_value_double(env, element, &value) == napi_ok) {
                        result[i] = static_cast<uint8_t>(value);
                    }
                }
            }
        }
        return result;
    }

    void *data = nullptr;
    size_t length = 0;
    status = napi_get_arraybuffer_info(env, byteArray, &data, &length);
    if (status != napi_ok || !data) {
        return result;
    }

    result.assign(static_cast<uint8_t *>(data), static_cast<uint8_t *>(data) + length);
    return result;
}

NativeResource<napi_value> VecStringToNapiStringList(napi_env env, const std::vector<std::string> &strs)
{
    napi_value array;
    napi_status status = napi_create_array_with_length(env, strs.size(), &array);
    if (status != napi_ok) {
        return NativeResource<napi_value>(nullptr, env);
    }

    for (size_t i = 0; i < strs.size(); i++) {
        auto str = MakeNapiString(env, strs[i]);
        if (str.get()) {
            status = napi_set_element(env, array, static_cast<uint32_t>(i), str.get());
        }
    }

    return NativeResource<napi_value>(array, env);
}
} // namespace ohos_rive